using System.Collections.Generic;

namespace DanMuHelper.Pool
{
    public sealed class PoolOfList<T> : Pool<List<T>>
    {
        public PoolOfList(int maxSize, int initCapacity = 8, bool trace = false)
            : this(
                () => new List<T>(),
                null,
                (o) => o.Clear(),
                null,
                maxSize: maxSize,
                initCapacity: initCapacity,
                trace: trace
            ) { }

        private PoolOfList(
            CreateFunc createFunc,
            PreGetFunc preGetFunc,
            ReturnFunc returnFunc,
            DestroyFunc destroyFunc,
            int maxSize,
            int initCapacity = 8,
            bool trace = false,
            IEqualityComparer<List<T>> comparer = null
        )
            : base(
                createFunc,
                preGetFunc,
                returnFunc,
                destroyFunc,
                maxSize,
                initCapacity,
                trace,
                comparer
            ) { }
    }

    /// <summary>
    /// 普通对象池
    /// </summary>
    public class Pool<T> : IPool
    {
        public delegate T CreateFunc();
        public delegate void PreGetFunc(T obj, bool justCreate);
        public delegate void ReturnFunc(T obj);
        public delegate void DestroyFunc(T obj);

        private readonly Stack<T> _pool;
        private readonly CreateFunc _createFunc;
        private readonly PreGetFunc _getFunc;
        private readonly ReturnFunc _returnFunc;
        private readonly DestroyFunc _destroyFunc;
        private readonly int _maxSize;

        #region 调试
        private readonly bool _trace;

        private readonly HashSet<T> _poolSet;
        private readonly HashSet<T> _usingSet;

        /// <summary>
        /// 计算使用中对象 (池外)
        /// </summary>
        public int CountUsing => _usingSet?.Count ?? 0;
        #endregion

        /// <summary>
        /// 计数未使用对象 (池内)
        /// </summary>
        public int CountUnused => _pool.Count;

        public object ObjectType => typeof(T);

        /// <summary>
        /// 普通对象池
        /// </summary>
        /// <param name="createFunc">创建方法</param>
        /// <param name="preGetFunc">获取事件, 参数为 (obj, 是否是刚刚创建)</param>
        /// <param name="returnFunc">回收事件</param>
        /// <param name="destroyFunc">销毁方法, 数量超过maxSize, Retrun时销毁 (一般不需要, 但是如果T中包含引用类型, 最好置为null</param>
        /// <param name="maxSize">容量上限, 小于0表示无上限</param>
        /// <param name="initCapacity">Stack初始容量, 优化内存分配, 避免Stack频繁扩容</param>
        /// <param name="trace">是否跟踪所有引用, 调试或需要验证回收对象是否源自缓存时使用</param>
        public Pool(
            CreateFunc createFunc,
            PreGetFunc preGetFunc,
            ReturnFunc returnFunc,
            DestroyFunc destroyFunc,
            int maxSize,
            int initCapacity = 8,
            bool trace = false,
            IEqualityComparer<T> comparer = default
        )
        {
            _pool = new Stack<T>(initCapacity > 0 ? initCapacity : 8);
            _createFunc = createFunc;
            _getFunc = preGetFunc;
            _returnFunc = returnFunc;
            _destroyFunc = destroyFunc;
            _maxSize = maxSize;
            _trace = trace;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
            if (trace)
            {
                _usingSet = new HashSet<T>(initCapacity, comparer);
                _poolSet = new HashSet<T>(initCapacity, comparer);
            }

            // PoolMonitor.AddPool(this);
#endif
        }

        public string Id { get; set; }

        public T Get()
        {
            if (_pool.Count > 0)
            {
                var obj = _pool.Pop();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                if (_trace)
                {
                    _usingSet.Add(obj);
                }
#endif
                _getFunc?.Invoke(obj, false);
                return obj;
            }
            else
            {
                var obj = _createFunc();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                if (_trace)
                {
                    _usingSet.Add(obj);
                }
#endif
                _getFunc?.Invoke(obj, true);
                return obj;
            }
        }

        public void Return(T obj)
        {
#if UNITY_EDITOR || DEVELOPMENT_BUILD
            if (_trace)
            {
                if (!_usingSet.Contains(obj))
                {
                    throw new System.Exception($"Returning object not from pool<{typeof(T)}>");
                }
                _usingSet.Remove(obj);
                if (_poolSet.Contains(obj))
                {
                    throw new System.Exception($"Returning object already in pool<{typeof(T)}>");
                }
            }
#endif
            if (_pool.Count < _maxSize || _maxSize < 0)
            {
                _returnFunc?.Invoke(obj);
                _pool.Push(obj);
            }
            else
            {
                _destroyFunc?.Invoke(obj);
            }
        }
    }
}
